/*    Copyright 2010 Tobias Marschall
 *
 *    This file is part of MoSDi.
 *
 *    MoSDi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    MoSDi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoSDi.  If not, see <http://www.gnu.org/licenses/>.
 */

package mosdi.util;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import joptsimple.OptionParser;
import joptsimple.OptionSet;
import mosdi.subcommands.Subcommand;

/** Class representing a command-line application consisting of several subcommands. */
public class SubcommandApplication {

	private String applicationName;
	private List<Subcommand> subcommandList;
	private Map<String,Subcommand> subcommandMap;
	
	private static SubcommandApplication instance = null;
	
	public SubcommandApplication(String applicationName, List<Subcommand> subcommandList) {
		this.applicationName = applicationName;
		this.subcommandList = subcommandList;
		subcommandMap = new HashMap<String,Subcommand>();
		for (Subcommand subcommand : subcommandList) {
			subcommandMap.put(subcommand.name(), subcommand);
		}
		instance = this;
	}

	public static SubcommandApplication getInstance() {
		return instance;
	}
	
	public String getApplicationName() {
		return applicationName;
	}

	/** Appends spaces to the right. */
	private static String printFixedWidth(int width, String s) {
		if (width<s.length()) throw new IllegalArgumentException();
		StringBuffer sb = new StringBuffer(width);
		sb.append(s);
		for (int i=s.length(); i<width; ++i) sb.append(" ");
		return sb.toString();
	}
	
	private void usage() {
		int w = 0;
		for (Subcommand s : subcommandList) {
			w = Math.max(w, s.name().length());
		}
		w += 2;
		Log.errorln(
				"Usage: " + applicationName + " [global-options] <command> <command-arguments>\n" +
				"\n" +
				"Options: \n" +
				"  -v [verbosity]: 0: default, 1: verbose, 2: debug\n" +
				"  -t: print times\n" +
				"\n" +
				"Valid commands:"
		);
		StringBuffer sb = new StringBuffer();
		for (Subcommand s : subcommandList) {
			sb.append(printFixedWidth(w,"  "+s.name()));
			StringTokenizer st = new StringTokenizer(s.description(), " ", false);
			int n = w;
			while (st.hasMoreTokens()) {
				String token = st.nextToken();
				if (n+token.length()>=80) {
					sb.append("\n");
					sb.append(printFixedWidth(w,""));
					n = w;
				}
				sb.append(" "+token);
				n+=token.length();
			}
			sb.append("\n");
		}
		Log.errorln(sb.toString());
		System.exit(1);
	}

	public void run(String[] args) {
		// determine subcommand position
		int subcmdPos;
		for (subcmdPos = 0; subcmdPos < args.length; ++subcmdPos) {
			if (subcommandMap.containsKey(args[subcmdPos])) {
				break;
			}
		}

		if (subcmdPos >= args.length) {
			usage();
			System.exit(1);
		}

		String[] globalArgs = Arrays.copyOfRange(args, 0, subcmdPos);

		// workaround for bug in eclipse profiler: an empty argument is added...
		// we know, there must be a real arg cause we already found the subcommand
		int firstEmptyArg = 0;
		for (firstEmptyArg = args.length - 1; firstEmptyArg >= 0; --firstEmptyArg) {
			if (!args[firstEmptyArg].equals("")) {
				// found last not empty arg
				// so go to first empty arg
				firstEmptyArg++;
				break;
			}
		}

		String[] subcmdArgs = Arrays.copyOfRange(args, subcmdPos + 1, firstEmptyArg);

		OptionParser optionParser = new OptionParser("v::t");
		OptionSet options = optionParser.parse(globalArgs);

		int verbosity = 0;
		if (options.has("v")) {
			if (options.hasArgument("v")) {
				verbosity = Integer.parseInt((String)options.valueOf("v"));
			} else {
				verbosity = 1;
			}
		}

		switch(verbosity){
		case 0: Log.setLogLevel(Log.Level.STANDARD); break;
		case 1: Log.setLogLevel(Log.Level.VERBOSE); break;
		case 2: Log.setLogLevel(Log.Level.DEBUG); break;
		default: 
			Log.errorln("Error: Invalid argument to option -v.");
			System.exit(1);
		}
		Log.printf(Log.Level.DEBUG, "Log level is set to %s%n", Log.getLogLevel());

		// Whether times should be logged
		Log.setTimingActive(options.has("t"));
		Log.printf(Log.Level.DEBUG, "Timing is active: %s%n", Log.isTimingActive());

		subcommandMap.get(args[subcmdPos]).run(subcmdArgs);
	}
	
}
